package com.eksframework.commons.core.converter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

/**
 * Integer转换器
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-04-16 16:20
 * @blame Team
 */
public class IntegerConverter<T> extends AbstractNumberConverter<T> {

    /**
     * int默认值
     */
    private static int intDefaultValue;
    /**
     * Integer对象
     */
    private Integer integerObject;

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * Constructs an instance with the value present.
     */
    public IntegerConverter(final T object) {
        super(object);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * * 创建实例
     *
     * @param object 输入参数
     * @return not null
     */
    @Nonnull
    public static <T> IntegerConverter of(@Nullable final T object) {
        return new IntegerConverter<>(object);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * get rounding mode to apply
     *
     * @param roundingMode rounding mode
     * @return RoundingMode
     * @see RoundingMode
     */
    @Override
    RoundingMode getRoundingMode(RoundingMode roundingMode) {
        return roundingMode == null ? RoundingMode.DOWN : roundingMode;
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>
     *
     * @param value        {@code Object} to convert to a {@code Integer}.
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return a {@code Integer}, not null
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Integer}.
     * @throws ArithmeticException   if {@code roundingMode==UNNECESSARY}
     *                               and the specified scaling operation would require rounding.
     * @see BigDecimalConverter#toBigDecimalThrow(int, RoundingMode)
     */
    private Integer convert(@Nullable final Object value, @Nullable final RoundingMode roundingMode) {
        Objects.requireNonNull(value);
        if (integerObject != null) {
            return integerObject;
        } else if (Integer.class.equals(value.getClass())) {
            this.integerObject = (Integer) value;
        } else if (Boolean.class.equals(value.getClass())) {
            this.integerObject = Boolean.parseBoolean(value.toString()) ? 1 : 0;
        } else {
            final BigDecimal bigDecimal = BigDecimalConverter.of(super.object)
                    .toBigDecimalThrow(0, getRoundingMode(roundingMode));
            if(bigDecimal.compareTo(new BigDecimal(Integer.MAX_VALUE)) > 0){
                this.integerObject = Integer.MAX_VALUE;
            }else if(bigDecimal.compareTo(new BigDecimal(Integer.MIN_VALUE)) < 0){
                this.integerObject = Integer.MIN_VALUE;
            }else{
                this.integerObject = bigDecimal.intValue();
            }
        }
        return integerObject;
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @return int
     * @see #intValue(RoundingMode)
     */
    public int intValue() {
        return intValue(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return int
     * @see #intValueDefault(int, RoundingMode)
     */
    public int intValue(@Nullable final RoundingMode roundingMode) {
        return intValueDefault(intDefaultValue, roundingMode);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @return int
     * @see #intValueDefault(int, RoundingMode)
     */
    public int intValueDefault(final int defaultValue) {
        return intValueDefault(defaultValue, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return int
     * @see #convert(Object, RoundingMode)
     */
    public int intValueDefault(final int defaultValue, @Nullable final RoundingMode roundingMode) {
        try {
            return super.object == null ? defaultValue : convert(super.object, roundingMode);
        } catch (Exception ex) {
            return defaultValue;
        }
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>
     *
     * @return int
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Integer}.
     * @see #toIntegerThrow(RoundingMode)
     */
    public int intValueThrow() {
        return toIntegerThrow(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>int</code>
     *
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return int
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Integer}.
     * @throws ArithmeticException   if {@code roundingMode==UNNECESSARY}
     *                               and the specified scaling operation would require rounding.
     * @see RoundingMode
     * @see #convert(Object, RoundingMode)
     */
    public int intValueThrow(@Nullable final RoundingMode roundingMode) {
        return convert(super.object, roundingMode);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @return a {@code Integer}, not null
     * @see #toInteger(RoundingMode)
     */
    @Nonnull
    public Integer toInteger() {
        return toInteger(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return a {@code Integer}, not null
     * @see #toIntegerDefault(Integer, RoundingMode)
     */
    @Nonnull
    public Integer toInteger(@Nullable final RoundingMode roundingMode) {
        return Objects.requireNonNull(toIntegerDefault(intDefaultValue, roundingMode));
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @return a {@code Integer}, may be null
     * @see #convert(Object, RoundingMode)
     */
    @Nullable
    public Integer toIntegerDefault(@Nullable final Integer defaultValue) {
        return toIntegerDefault(defaultValue, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return a {@code Integer}, may be null
     * @see #toIntegerDefault(Integer)
     */
    @Nullable
    public Integer toIntegerDefault(
            @Nullable final Integer defaultValue, @Nullable final RoundingMode roundingMode) {
        try {
            return super.object == null ? defaultValue : convert(super.object, roundingMode);
        } catch (NumberFormatException nfe) {
            return defaultValue;
        }
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>
     *
     * @return a {@code Integer}, not null
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Integer}.
     * @see #toIntegerThrow(RoundingMode)
     */
    @Nonnull
    public Integer toIntegerThrow() {
        return toIntegerThrow(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Integer</code>
     *
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.DOWN
     * @return a {@code Integer}, not null
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Integer}.
     * @throws ArithmeticException   if {@code roundingMode==UNNECESSARY}
     *                               and the specified scaling operation would require rounding.
     * @see RoundingMode
     * @see #convert(Object, RoundingMode)
     */
    @Nonnull
    public Integer toIntegerThrow(@Nullable final RoundingMode roundingMode) {
        return convert(super.object, roundingMode);
    }
}